﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Web.Security;
using System.Runtime.CompilerServices;
using log4net;

namespace Security
{
    public sealed class TripLoaUserManager
    {
        private static UserCache userCache;
        private static int instanceCall;

        private TripLoaUserManager()
        {
            userCache = new UserCache();
            instanceCall = 0;
        }

        private class ManagerCreator
        {
            static ManagerCreator()
            {
            }

            internal static readonly TripLoaUserManager _instance = new TripLoaUserManager();
        }
        /// <summary>
        /// Returns unique instance of TriploaUserManager
        /// </summary>
        public static TripLoaUserManager Instance
        {
            get
            {        
                Interlocked.Increment(ref instanceCall);
                return ManagerCreator._instance;
            }
        }
        
        /// <summary>
        /// Add a user to the cache.
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        internal static bool addUser(String userName)
        {
            validateUsername(userName);
            
            userCache.AddUserToCache(userName);

            return true;
        }

        /// <summary>
        /// Method is deprecated. Use <see cref="getUser(string)"/> instead.
        /// </summary>
        /// <returns></returns>
        [Obsolete("This method is deprecated. Use getUser(String userName) instead.", false)]   // Quando smetteranno di usarlo si metterà true
        public TripLoaUser getUser()
        {
            throw new DeprecatedMethodException("This method is deprecated. Use getUser(String userName) instead.");
        }

        /// <summary>
        /// Get a <see cref="TripLoaUser"/> object with all informations about the user (SecurityUser, AnonymizerUser, Profile) aggregated.
        /// </summary>
        /// <param name="userName">The userName of the logged user (User.Identity.Name)</param>
        /// <returns>A <see cref="TripLoaUser"/></returns>
        public TripLoaUser getUser(String userName)
        {
            validateUsername(userName);

            return userCache[userName];
        }

        //[MethodImpl(MethodImplOptions.Synchronized)]
        //Method doesn't need to be synchronized
        internal static Boolean removeUser(String userName)
        {
            validateUsername(userName);

            return userCache.RemoveUserFromCache(userName);
        }


        private static void validateUsername(String userName)
        {
            if (String.IsNullOrEmpty(userName))
                throw new InvalidUserNameException("Null or invalid username");
        }

        /// <summary>
        /// Check if a user is logged or not
        /// </summary>
        /// <returns>True if the user is logged, false otherwise</returns>
        public static bool isUserCached(string userName)
        {
            return userCache.isUserCached(userName);
        }
    }

    /// <summary>
    /// A cache used to store currently logged in <see cref="TripLoaUser"/>.
    /// </summary>
    internal class UserCache
    {
        private static Dictionary<String, WeakReference> _userList;
        private int userRegenerationCount;
        private int userRequests;
        static private ReaderWriterLock sync;
#if LOG
        private readonly ILog _logger = LogManager.GetLogger(typeof(UserCache));
#endif
        /// <summary>
        /// Default constructor.
        /// </summary>
        public UserCache()
        {
            _userList = new Dictionary<string, WeakReference>();
            userRegenerationCount = 0;
            userRequests = 0;
            sync = new ReaderWriterLock();
        }
        
        /// <summary>
        /// Accesses a <see cref="TripLoaUser"/> object from the cache.
        /// If the object was reclaimed for garbage collection,
        /// create a new <see cref="TripLoaUser"/> object.
        /// </summary>
        /// <param name="userName">The username of the currently logged in user</param>
        /// <returns>A <see cref="TripLoaUser"/> which contains all user data (Security & Anonymizer Profile)</returns>
        public TripLoaUser this[String userName]
        {
            get
            {
#if LOG
                _logger.Debug("TripLoaUser this: " + userName);
#endif
                TripLoaUser loaUser;
                if (_userList.ContainsKey(userName))
                {
                    loaUser = _userList[userName].Target as TripLoaUser;
                    if(loaUser == null)
                    {
                        loaUser = new TripLoaUser(userName);
                        _userList[userName] = new WeakReference(loaUser);
                        Interlocked.Increment(ref userRegenerationCount);
                        Interlocked.Increment(ref userRequests);
                    }
                    else
                    {
                        //TODO Aggiornare il Profile per refreshare il riferimento allo user di storage...
                        //TODO andrà cancellato questo metodo una volta aggiornata la classe profile
                       // ((TripLoaUser)(_userList[userName].Target)).refreshProfile();
                        Interlocked.Increment(ref userRequests);
                    }
                }
                else
                {
                    loaUser = AddUserToCache(userName); 
                }
                
                return loaUser;
            }
        }

        /// <summary>
        /// Gets the number of entries contained into the <see cref="UserCache"/>.
        /// </summary>
        public int size
        {
            get
            {
                return _userList.Count;
            }
        }

        /// <summary>
        /// Add a user to the <see cref="UserCache"/>.
        /// </summary>
        /// <param name="userName">The currently logged user's username</param>
        //[MethodImpl(MethodImplOptions.Synchronized)]
        internal TripLoaUser AddUserToCache(string userName)
        {
#if LOG
            _logger.Debug("AddUserToCache: " + userName);
#endif

            //sync.AcquireReaderLock(Timeout.Infinite);
            TripLoaUser loaUser = null;
            /*if (!_userList.ContainsKey(userName))
            {*/
                loaUser = new TripLoaUser(userName);
                sync.AcquireWriterLock(Timeout.Infinite);
                try
                {
                    _userList.Add(userName, new WeakReference(loaUser));
                }
                    catch(ArgumentException)
                    {
                        _userList[userName] = new WeakReference(loaUser);
                    }
                finally
                {
                    sync.ReleaseWriterLock();
                }
           /* }
            else
            {
                sync.ReleaseReaderLock();
            }*/
            return loaUser;
        }

        /// <summary>
        /// Removes a user from the <see cref="UserCache"/>.
        /// </summary>
        /// <param name="userName">The username of the user to be removed.</param>
        /// <returns>True if the user was removed, false otherwise.</returns>
        //[MethodImpl(MethodImplOptions.Synchronized)]
        internal bool RemoveUserFromCache(String userName)
        {
#if LOG
            _logger.Debug("RemoveUserFromCache: " + userName);
#endif

            sync.AcquireWriterLock(Timeout.Infinite);
            bool success = false;
            try
            {
                if (_userList.ContainsKey(userName))
                    success = _userList.Remove(userName);
            }
            finally
            {
                sync.ReleaseWriterLock();
            }
            
            return success;
        }

        internal bool isUserCached(String userName)
        {
#if LOG
            _logger.Debug("isUserCached: " + userName);
#endif

            bool success = false;

            if(userName!=null)
                success = _userList.ContainsKey(userName);
            
            return success;
        }
    }
}